/**
 * @file   read_video_thread.cpp
 * @author Pedro Jimenez
 * @date   Fri Nov 14 15:01:18 2008
 * 
 * @brief  This class is used to read the stereo videos in raw format grabbed with the ./capturar program 
 * 
 * 
 */
#include <iostream>

#include <string.h>

#include <sys/time.h>
#include <unistd.h>
#include <sys/select.h>

#include "cv.h"
#include "cxcore.h"
#include "highgui.h"
#include "frame_header.hh"

#include "Producer.hpp"
#include "read_video_thread.h"

using namespace std;

///will supported formats in version 1. This version, 2.0 only supports raw format
char video_formats_strings[][6] = {"", "raw", "ra2", "avi", "error"};

CRead_Video_Thread::CRead_Video_Thread (int capacity) : CProducer<Cstereo_IplImage>(capacity)
{
    video_size_out = cvSize(0,0);
}
CRead_Video_Thread::~CRead_Video_Thread()
{
    stop();
}

 
/** 
 * \brief Initializes the video files, the params of the videos, 
 * and the buffers stored in \c CProducer::m_container circular_buffer
 * 
 * This function must initialize the data which will be contained in the circular_buffer, and push the buffer in by calling  push_buffer(& pItem)
 *
 * This function must finish with a call to 
 * <pre>return \c CProducer<Cstereo_IplImage>::start() </pre>
 * to start the execution of the thread with the producer code.
 * 
 * The code of this function could go on the \c CRead_Video_Thread::operator()() overridden function as well 
 * @return true if the producer thread could be started correctly 
 */
bool CRead_Video_Thread::start() 
{
    Cstereo_IplImage simg;
    int i;
    off_t curr;
    frame_header head[2];

    //inicializar todo lo relacionado con el archivo de video e imágenes
    for (i=0; i<2; i++)
    {
	if (!(rawvideo[i] = fopen(video_name[i].c_str(), "r")))
	    return false;
	//get images size
	curr = ftello(rawvideo[i]);
	fread(&head[i], sizeof(frame_header), 1, rawvideo[i]);
	video_size[i].width  = head[i].width;
	video_size[i].height = head[i].height;
	readImageSize[i] = video_size[i].width * video_size[i].height;

	printf("Video %d (%s):  width: %d, height: %d\n", i, (i?"right":"left"),
	       video_size[i].width, video_size[i].height);

	//elimina las imágenes que no valgan al principio del video
	//while (head[i].nframe != (unsigned int) iniframe)// > 1 || (iniframe > 0 && head[i].nframe < (unsigned int)iniframe ))
	while (head[i].nframe > 1 )
	{	
	    fseeko(rawvideo[i], curr + readImageSize[i], SEEK_SET);
	    //printf("Video %d, Skipping frame %d\n", i, head[i].nframe);
	    curr = ftello(rawvideo[i]);
	    fread(&head[i], sizeof(frame_header), 1, rawvideo[i]);
	}   
	fseeko(rawvideo[i], curr, SEEK_SET);
	
	if (iniframe > 0)
	    fseeko(rawvideo[i], iniframe*readImageSize[i], SEEK_CUR);
    }

    video_channels = 1;
    if (video_size_out.width == 0)
	video_size_out.width = video_size[0].width;
    if (video_size_out.height == 0)
	video_size_out.height = video_size[0].height;


    //conversion del tamaño de la imagen si hace falta
    for (i=0; i<2; i++)
    {
	if (video_size_out.width == video_size[i].width && 
	    video_size_out.height ==video_size[i].height )
	    img_not_equal_img_ori[i] = false;
	else
	{
	    img_not_equal_img_ori[i] = true; 
	    if (video_size[i].width > video_size_out.width)
		roi[i].width = roi_out[i].width = video_size_out.width;
	    else
		roi[i].width = roi_out[i].width = video_size[i].width;

	    roi_out[i].x = (video_size_out.width - roi_out[i].width)/2;
	    roi[i].x = (video_size[i].width - roi[i].width)/2;

	    if (video_size[i].height > video_size_out.height)
		roi[i].height = roi_out[i].height = video_size_out.height;
	    else
		roi[i].height = roi_out[i].height = video_size[i].height;

	    roi_out[i].y = (video_size_out.height - roi_out[i].height)/2;
	    roi[i].y = (video_size[i].height - roi[i].height)/2;
	}

	img_clone[i] = cvCreateImage(video_size_out, IPL_DEPTH_8U, 1);
    }
    

    //inicialización de los IplImage que iran al circular_buffer
    simg.n = 0;
    do {
	for (i=0; i<2; i++)
	{
	    simg.img[i] = cvCreateImage(video_size_out, IPL_DEPTH_8U, 1);
	    cvZero(simg.img[i]);
	    if (img_not_equal_img_ori[i])
	    {
		simg.img_ori[i] = cvCreateImage(video_size[i], IPL_DEPTH_8U, 1);
		cvZero(simg.img_ori[i]);
	    }
	    else
		simg.img_ori[i] = simg.img[i];
	}
	simg.n ++;
    } while ( push_buffer(simg) );
    printf("Read Video Thread Started\n");
    return CProducer<Cstereo_IplImage>::start();
}

/*void CRead_Video_Thread::operator()() 
  {
  CProducer<Cstereo_IplImage>::operator()();
  //return true;
  //return CProducer<Cstereo_IplImage>::start();
  }
*/

/** 
 * \brief Read form disk each video frame and stores it in the circular_buffer. 
 *
 * This function is executed on each loop of the producer thread. Here is where the video data has to be read from file and stored on the circular_buffer so that the 
 * consumer can use it.
 * 
 * @param pbuf empty buffer where to store a new frame of the buffer
 * 
 * @return true to go on with the execution of the producer thread.
 * @return false to stop the thread, if no more frames to read.
 */
bool CRead_Video_Thread::producer_callback(Cstereo_IplImage* pbuf)
{
    pbuf->head[0] = read_image(pbuf->img_ori[0], rawvideo[0], video_size[0]);
    pbuf->head[1] = read_image(pbuf->img_ori[1], rawvideo[1], video_size[1]);
	
    int difft = (int)difftime_ms(pbuf->head[0]->filltime, pbuf->head[1]->filltime);
    while (abs(difft) > 15)
    {
	if (difft > 0)
	    pbuf->head[1] = read_image(pbuf->img[1], rawvideo[1], video_size[1]);
	else 
	    pbuf->head[0] = read_image(pbuf->img[0], rawvideo[0], video_size[0]);

	difft=(int)difftime_ms(pbuf->head[0]->filltime, pbuf->head[1]->filltime);

	//comprueba si se ha acabado el archivo
	if (feof(rawvideo[0]) || feof(rawvideo[1]))
	{
	    printf("End of video\n");
	    fflush(stdout);
	    //para salir del bucle del CProducer, simplemente se devuelve false
	    return false;
	}
    }
    
    //if the output image is not the same size than the original
    for (int i=0; i<2; i++)
    {
	if (img_not_equal_img_ori[i])
	{
	    cvSetImageROI(pbuf->img[i], roi_out[i]);
	    cvSetImageROI(pbuf->img_ori[i], roi[i]);
	    cvCopy(pbuf->img_ori[i], pbuf->img[i]);
	    cvResetImageROI(pbuf->img[i]);
	    cvResetImageROI(pbuf->img_ori[i]);
	}
    }

    //return true si se quiere seguir con el bucle del productor
    return true;
}
/** 
 * \brief stops the execution of the reading thread, and free the buffers of the circular_buffer
 * 
 */
void CRead_Video_Thread::stop()
{
    CProducer<Cstereo_IplImage>::stop();

    Cstereo_IplImage * pbuf;
    while ((pbuf = pop_buffer()))
    {
	for (int i=0; i<2; i++)
	{
	    cvReleaseImage(&pbuf->img[i]);
	    if (img_not_equal_img_ori[i])
		cvReleaseImage(&pbuf->img_ori[i]);
	}
    }

    for (int i=0; i<2; i++)
    {
	cvReleaseImage(&img_clone[i]);
	if (rawvideo[i])
	    fclose(rawvideo[i]);
	rawvideo[i] = 0;
    }
    printf("fin de la captura de video\n");	
}
/** 
 * \brief Gets a new frame from the circular_buffer to use it.
 * 
 * This function gets a new from the circular_buffer, where the producer put it. It prints some imformation about the frames
 * @return the video frame
 */
Cstereo_IplImage* CRead_Video_Thread::consumer()
{
    //primero toma el siguiente buffer libre
    Cstereo_IplImage * pbuf = CProducer<Cstereo_IplImage>::consumer();
	
    //ahora hace todo lo necesario con el buffer, antes de devolverlo
    std::cout << CGREEN_B_L << " --------------- Frame: " << 
	pbuf->head[0]->nframe << 
	" ----------------"<< CNORMAL << std::endl;
    printf("Frame %d-> fps: %5.2f, shutter %d.\n",pbuf->head[0]->nframe, 
	   1000.0/difftime_ms(pbuf->head[0]->filltime, oldfilltime[0]), 
	   pbuf->head[0]->shutter_usec);
    printf("Frame %d-> fps: %5.2f, shutter %d.\n",pbuf->head[1]->nframe, 
	   1000.0/difftime_ms(pbuf->head[1]->filltime, oldfilltime[1]), 
	   pbuf->head[1]->shutter_usec);
    printf("difftime left-right frame: %5.2f ms\n", 
	   difftime_ms(pbuf->head[0]->filltime, pbuf->head[1]->filltime));

    oldfilltime[1] = pbuf->head[1]->filltime;
    oldfilltime[0]= pbuf->head[0]->filltime;
    	
    //devuelve el buffer para su uso en la aplicación
    return pbuf;
}

/** 
 * \brief Initialize some params of the video
 * 
 * This function Initialize the file names of the video files, the format and the first frame to read
 * @param filename of the videos. See at the wiki [readvideo] the possibler input formats
 * @param format the format of the video. Only supports "raw" format. If blank, "raw" is assumed. 
 * @param frame the first frame to read
 * 
 * @return true if the initialization was correct.
 */bool CRead_Video_Thread::init_params(const char *filename, const char * format, int frame)
{

    if(strlen(filename) == 0)
    {
	printf("Not enough videos.\n");
	return (int)READVIDEO_ERROR_FILENAME_ERROR;
    }
    video_name[0].assign(filename);

    size_t dot = video_name[0].find_last_of('.');

    //busca el formato del archivo de video
    if (strcasecmp(format, video_formats_strings[(int)raw_video]) == 0)
    {
	video_format = raw_video;
    }
    else if (strcasecmp(format, video_formats_strings[(int)ra2_video]) == 0)
    {
	video_format = ra2_video;
    }
    else if (strcasecmp(format, video_formats_strings[(int)avi_video]) == 0)
    {
	video_format = avi_video;
    }
    else 
    {
	 
	if (dot == string::npos) //no se ha encontrado
	    video_format = raw_video;
	else
	{
	    const char * extension = video_name[0].substr(dot+1).c_str();
	    if (strcasecmp(extension, video_formats_strings[(int)avi_video]) == 0)
		video_format = avi_video;
	    else if (strcasecmp(extension, video_formats_strings[(int)raw_video]) == 0)
		video_format = raw_video;
	    else if (strcasecmp(extension, video_formats_strings[(int)ra2_video]) == 0)
		video_format = ra2_video;
	    else
	    {
		video_format = no_format;
		return (int)READVIDEO_ERROR_FORMAT_ERROR;
	    }
	}
    }

    printf("video format: %d - %s\n", video_format, video_formats_strings[video_format]);
    //extrae los nombres de los videos, según el formato indicado

    switch (video_format)
    {
    case raw_video:
	if (dot == std::string::npos)
	    video_name[0] += "1.raw";

	video_name[1] = get_pathbasename();
	video_name[1][video_name[1].size()-1] = '2';
	video_name[1] += ".raw";
	break;
    case no_format:
	printf("Error de formato del video.\n");
	printf("Formato de video erróneo\n");
	return (int)READVIDEO_ERROR_FORMAT_NOTSUPPORTED;
	break;
    case format_error:
	printf("Error de formato del video.\n");
	printf("Formato de video erróneo\n");
	return (int)READVIDEO_ERROR_FORMAT_ERROR;
	break;
    default:
	printf("Error de formato del video.\n");
	printf("Formato de video no soportado\n");
	return (int)READVIDEO_ERROR_FORMAT_NOTSUPPORTED;
	break;
    }
    
    cout << "Video 1:     " << video_name[0] << endl;
    cout << "Video 2:     " << video_name[1] << endl;
	
    //Mira si hay un archivo de configuración del video, 
    // y si lo hay, busca el frame de inicio
    if (frame < 0)
    {
	std::string configfile = get_pathbasename() + ".ini";
	 
	FILE *fini = fopen(configfile.c_str(), "r");
	if (!fini)
	    frame = 0;
	else
	{
	    fscanf(fini, "%d", &frame);
	    if (frame < 0) 
		frame = 0;
	}
    }
    iniframe = frame;

    //return 0 indica que todo fue bien
    return 0;
}

/** 
 * \brief Returns the base name of the first video file 
 *  
 * @return a constant buffer to the base name of the first video.
 */
std::string CRead_Video_Thread::get_pathbasename()
{
    return video_name[0].substr(0, video_name[0].find_last_of('.')); 
}


IplImage * CRead_Video_Thread::img(int n)
{
    return get_last_used()->img[n];
}

frame_header * CRead_Video_Thread::head(int n)
{
    return get_last_used()->head[n];
}

void CRead_Video_Thread::set_image_size(CvSize size)
{
    video_size_out = size;
}

void CRead_Video_Thread::set_image_size(int width, int height)
{
    video_size_out = cvSize(width, height);
}


IplImage * CRead_Video_Thread::get_cloneimage(int n)
{
    return img_clone[n];
}

IplImage ** CRead_Video_Thread::cloneimages(int n)
{
    if (n<0)
    {
	for (int i=0; i<2; i++)
	    cvCopy(img(i), img_clone[i]);
	return img_clone;
    }
    else
    {
	cvCopy(img(n), img_clone[n]);
	return &img_clone[n];
    }
}
